
//  --------------------------------------------------------------------------------------
//  FeedSyncHelpers.js
//
//  BIG HONKING NOTE:  You MUST include MiscellaneousHelpers.js before including this file
//  --------------------------------------------------------------------------------------

var g_FeedSyncNamespaceURI = "http://feedsync.org/2007/feedsync";

function FeedSyncItemCollectionClass(i_XMLDOMDocument)
    {
    this.m_XMLDOMDocument = i_XMLDOMDocument;
    
    var NamespaceDeclarations = new Array();
    NamespaceDeclarations[0] = "xmlns:sx='" + g_FeedSyncNamespaceURI + "'";

    var ItemContainerXPathPart;
    if (GetLocalName(i_XMLDOMDocument.documentElement) == "rss")
        ItemContainerXPathPart = "//rss/channel";
    else if (GetLocalName(i_XMLDOMDocument.documentElement) == "feed")
        {
        ItemContainerXPathPart = "//atom:feed";
        NamespaceDeclarations[1] = "xmlns:atom='" + g_AtomNamespaceURI + "'";
        }
    else
        throw "Unknown document type!";
        
    this.m_ItemContainerXMLDOMElement = SelectSingleNode
        (
        i_XMLDOMDocument.documentElement,
        ItemContainerXPathPart,
        NamespaceDeclarations,
        true
        );
        
    var FeedSyncItemXmlElements = SelectNodes
        (
        this.m_ItemContainerXMLDOMElement,
        "*[sx:sync]",
        NamespaceDeclarations,
        true
        );
        
    this.m_FeedSyncItems = new Object();
    
    for (var Index = 0; Index < FeedSyncItemXmlElements.length; ++Index)
        {
        var FeedSyncItemXmlElement = FeedSyncItemXmlElements[Index];
        var FeedSyncItem = new FeedSyncItemClass(FeedSyncItemXmlElement);
        
        this.m_FeedSyncItems[FeedSyncItem.m_SyncNode.m_ID] = FeedSyncItem;
        }

    function _CreateItem(i_FeedSyncID, i_Title, i_Description, i_By, i_When, i_Sequence)
        {
        if (IsNullOrEmpty(i_FeedSyncID))
            throw "Must provide Sync ID!";
        
        if (IsNullOrEmpty(i_Title))
            throw "Must provide title!";

        if (IsNullOrEmpty(i_Description))
            throw "Must provide description!";
            
        var Item = null;
        var ItemXMLDOMElement = null;
        
        var SyncXMLDOMElement = CreateXMLDOMElement
            (
            this.m_XMLDOMDocument,
            "sx:sync",
            g_FeedSyncNamespaceURI
            );
            
        SyncXMLDOMElement.setAttribute("id", i_FeedSyncID);
        SyncXMLDOMElement.setAttribute("updates", "1");
        SyncXMLDOMElement.setAttribute("deleted", "false");
        SyncXMLDOMElement.setAttribute("noconflicts", "false");

        var HistoryXMLDOMElement = CreateXMLDOMElement
            (
            this.m_XMLDOMDocument,
            "sx:history",
            g_FeedSyncNamespaceURI
            );

        if (i_Sequence == null)
            i_Sequence = "1";            
            
        HistoryXMLDOMElement.setAttribute("sequence", i_Sequence);

        if (IsNullOrEmpty(i_When))
            i_When = ConvertDateToRFC3339DateTime();

        HistoryXMLDOMElement.setAttribute("when", i_When);
        
        if (!IsNullOrEmpty(i_By))
            HistoryXMLDOMElement.setAttribute("by", i_By);

        SyncXMLDOMElement.appendChild(HistoryXMLDOMElement);

        if (GetLocalName(this.m_XMLDOMDocument.documentElement) == "rss")
            {
            ItemXMLDOMElement = CreateXMLDOMElement
                (
                this.m_XMLDOMDocument,
                "item",
                null
                );

            ItemXMLDOMElement.appendChild(SyncXMLDOMElement);
                            
            var TitleXMLDOMElement = CreateXMLDOMElement
                (
                this.m_XMLDOMDocument,
                "title",
                null
                );

            SetTextForXMLDOMNode(TitleXMLDOMElement, XMLEncode(i_Title));
            ItemXMLDOMElement.appendChild(TitleXMLDOMElement);

            var DescriptionXMLDOMElement = CreateXMLDOMElement
                (
                this.m_XMLDOMDocument,
                "description",
                null
                );

            SetTextForXMLDOMNode(DescriptionXMLDOMElement, XMLEncode(i_Description));
            ItemXMLDOMElement.appendChild(DescriptionXMLDOMElement);
            }
        else if (GetLocalName(this.m_XMLDOMDocument.documentElement) == "feed")
            {
            ItemXMLDOMElement = CreateXMLDOMElement
                (
                this.m_XMLDOMDocument,
                "atom:entry",
                g_AtomNamespaceURI
                );

            ItemXMLDOMElement.appendChild(SyncXMLDOMElement);

            var TitleXMLDOMElement = CreateXMLDOMElement
                (
                this.m_XMLDOMDocument,
                "atom:title",
                g_AtomNamespaceURI
                );

            SetTextForXMLDOMNode(TitleXMLDOMElement, XMLEncode(i_Title));
            ItemXMLDOMElement.appendChild(TitleXMLDOMElement);

            var ContentXMLDOMElement = CreateXMLDOMElement
                (
                this.m_XMLDOMDocument,
                "atom:content",
                g_AtomNamespaceURI
                );

            SetTextForXMLDOMNode(ContentXMLDOMElement, XMLEncode(i_Description));
            ItemXMLDOMElement.appendChild(ContentXMLDOMElement);

            var IDXMLDOMElement = CreateXMLDOMElement
                (
                this.m_XMLDOMDocument,
                "atom:id",
                g_AtomNamespaceURI
                );

            SetTextForXMLDOMNode(IDXMLDOMElement, "tag:feedsync.org," + i_FeedSyncID);
            ItemXMLDOMElement.appendChild(IDXMLDOMElement);

            var UpdatedXMLDOMElement = CreateXMLDOMElement
                (
                this.m_XMLDOMDocument,
                "atom:updated",
                g_AtomNamespaceURI
                );

            SetTextForXMLDOMNode(UpdatedXMLDOMElement, ConvertDateToRFC3339DateTime());
            ItemXMLDOMElement.appendChild(UpdatedXMLDOMElement);
            }

        return new FeedSyncItemClass(ItemXMLDOMElement);
        }
        
    function _AddItem(i_FeedSyncItem)
        {
        if (this.GetItem(i_FeedSyncItem.m_SyncNode.m_ID) != null)
            throw "Item already exists!";
        
        if (i_FeedSyncItem.m_XMLDOMElement.ownerDocument != this.m_XMLDOMDocument)
            {
            var ClonedDOMElement = i_FeedSyncItem.m_XMLDOMElement.cloneNode(true);
            i_FeedSyncItem = new FeedSyncItemClass(ClonedDOMElement);
            }
            
        this.m_ItemContainerXMLDOMElement.appendChild(i_FeedSyncItem.m_XMLDOMElement);
        this.m_FeedSyncItems[i_FeedSyncItem.m_SyncNode.m_ID] = i_FeedSyncItem;
        }

    function _GetAllItems()
        {
        var AllItemsArray = new Array();
        for (var Key in this.m_FeedSyncItems)
            AllItemsArray.push(this.m_FeedSyncItems[Key]);
            
        return AllItemsArray;
        }
        
    function _GetItem(i_FeedSyncID)
        {
        return this.m_FeedSyncItems[i_FeedSyncID];
        }

    function _ReplaceItem(i_FeedSyncID, i_FeedSyncItem)
        {
        var LocalItem = this.GetItem(i_FeedSyncID);
        if (LocalItem == null)
            throw "Item does not exist!";
        
        LocalItem.m_XMLDOMElement.parentNode.removeChild(LocalItem.m_XMLDOMElement);
        this.m_FeedSyncItems[i_FeedSyncID] = null;

        this.AddItem(i_FeedSyncItem);
        }
                
    function _MergeItemCollection(i_IncomingItemCollection)
        {
        var MergeChangeList = this.GetMergeChangeList(i_IncomingItemCollection);
        for (var Index = 0; Index < MergeChangeList.length; ++Index)
            {
            var MergeItem = MergeChangeList[Index];
            var LocalItem = this.GetItem(MergeItem.m_SyncNode.m_ID);
            
            if (LocalItem == null)
                this.AddItem(MergeItem);
            else
                this.ReplaceItem(MergeItem.m_SyncNode.m_ID, MergeItem);
            }
        }

    function _GetMergeChangeList(i_IncomingItemCollection)
        {
        var MergeChangeList = new Array();
        var IncomingItems = i_IncomingItemCollection.GetAllItems();
        
        for (var Index = 0; Index < IncomingItems.length; ++Index)
            {
            var MergedItem = null;
            var IncomingItem = IncomingItems[Index];
            var LocalItem = this.GetItem(IncomingItem.m_SyncNode.m_ID);

            if (LocalItem != null)
                {
                var CompareNodesResult = LocalItem.Compare(IncomingItem);
                var NeedToMerge = 
                    (CompareNodesResult == null) ||
                    (CompareNodesResult == -1) ||
                    (CompareNodesResult == 1) && (!IncomingItem.IsSubsumedBy(LocalItem));
                    
                if (!NeedToMerge)
                    continue;
                    
                MergedItem = LocalItem.Merge(IncomingItem);
                }
            else
                MergedItem = IncomingItem;
                
            MergeChangeList.push(MergedItem);
            }
            
        return MergeChangeList;
        }
        
    FeedSyncItemCollectionClass.prototype.CreateItem = _CreateItem;
    FeedSyncItemCollectionClass.prototype.AddItem = _AddItem;
    FeedSyncItemCollectionClass.prototype.GetItem = _GetItem;
    FeedSyncItemCollectionClass.prototype.GetAllItems = _GetAllItems;
    FeedSyncItemCollectionClass.prototype.ReplaceItem = _ReplaceItem;
    FeedSyncItemCollectionClass.prototype.MergeItems = _MergeItemCollection;
    FeedSyncItemCollectionClass.prototype.GetMergeChangeList = _GetMergeChangeList;
    }

function FeedSyncItemClass(i_XMLDOMElement)
	{
	this.m_XMLDOMElement = i_XMLDOMElement;
	this.m_SyncNode = new SyncNodeClass(this);
	
	function _UpdateItem(i_By, i_When, i_Sequence)
	    {
	    this.m_SyncNode.Update
	        (
	        i_By,
	        i_When,
	        i_Sequence,
	        false
	        );
	    }

	function _ResolveItemConflicts(i_By, i_When, i_Sequence)
	    {
	    this.m_SyncNode.Update
	        (
	        i_By,
	        i_When,
	        i_Sequence,
	        true
	        );
	    }

    function _DeleteItem(i_By, i_When, i_Sequence)
        {
	    this.m_SyncNode.Update
	        (
	        i_By,
	        i_When,
	        i_Sequence,
	        false,
	        true
	        );
        }

    function _CloneItem()
        {
    	var ClonedXMLDOMElement = this.m_XMLDOMElement.cloneNode(true);
	    var ClonedFeedSyncItem = new FeedSyncItemClass(ClonedXMLDOMElement);
    	return ClonedFeedSyncItem;
    	}

    function _MergeItem(i_IncomingFeedSyncItem)
        {
        //  Create flattened array for local items & conflicts
	    var LocalItemCollection = new Array();
	    var ClonedLocalNode = this.Clone();
	    var ClonedLocalSyncNode = ClonedLocalNode.m_SyncNode;
    	
	    for (var Index = 0; Index < ClonedLocalSyncNode.m_ConflictNodes.length; ++Index)
	        LocalItemCollection.push(ClonedLocalSyncNode.m_ConflictNodes[Index]);

	    if (ClonedLocalSyncNode.m_ConflictNodes.length > 0)
	        {
	        ClonedLocalSyncNode.m_ConflictsXMLDOMElement.parentNode.removeChild(ClonedLocalSyncNode.m_ConflictsXMLDOMElement);
	        ClonedLocalSyncNode.m_ConflictsXMLDOMElement = null;
	        ClonedLocalSyncNode.m_ConflictNodes.length = 0;
	        }
    	    
	    LocalItemCollection.push(ClonedLocalNode);

        //  Create flattened array for incoming items & conflicts
	    var IncomingItemCollection = new Array();
	    var ClonedIncomingNode = i_IncomingFeedSyncItem.Clone();
	    var ClonedIncomingSyncNode = ClonedIncomingNode.m_SyncNode;
    	
	    for (var Index = 0; Index < ClonedIncomingSyncNode.m_ConflictNodes.length; ++Index)
	        IncomingItemCollection.push(ClonedIncomingSyncNode.m_ConflictNodes[Index]);

	    if (ClonedIncomingSyncNode.m_ConflictNodes.length > 0)
	        {
	        ClonedIncomingSyncNode.m_ConflictsXMLDOMElement.parentNode.removeChild(ClonedIncomingSyncNode.m_ConflictsXMLDOMElement);
	        ClonedIncomingSyncNode.m_ConflictsXMLDOMElement = null;
	        ClonedIncomingSyncNode.m_ConflictNodes.length = 0;
	        }
    	    
	    IncomingItemCollection.push(ClonedIncomingNode);

	    var MergeResultItemCollection = new Array();

        //  Remove duplicates & subsumed items - also get the winner
	    var WinnerNode = ProcessCollections
	        (
	        LocalItemCollection, 
	        IncomingItemCollection, 
	        MergeResultItemCollection, 
	        WinnerNode
	        );
    	
	    WinnerNode = ProcessCollections
	        (
	        IncomingItemCollection, 
	        LocalItemCollection, 
	        MergeResultItemCollection, 
	        WinnerNode
	        );

	    var WinnerSyncNode = WinnerNode.m_SyncNode;
	    if (WinnerSyncNode.m_NoConflicts || (MergeResultItemCollection.length == 1))
	        return WinnerNode;
        
        //  Reconstruct conflicts for item
	    var WinnerConflictsXMLDOMElement = CreateXMLDOMElement
	        (
	        this.m_XMLDOMElement.ownerDocument,
	        "sx:conflicts",
	        g_FeedSyncNamespaceURI
	        );
        
	    WinnerSyncNode.m_XMLDOMElement.appendChild(WinnerConflictsXMLDOMElement);
	    WinnerSyncNode.m_ConflictsXMLDOMElement = WinnerConflictsXMLDOMElement;
        
	    var WinnerConflictNodes = new Array();
        
	    for (var Index = 0; Index < MergeResultItemCollection.length; ++Index)
	        {
	        var MergeResultItem = MergeResultItemCollection[Index];
            
	        if (0 == WinnerNode.Compare(MergeResultItem))
	            continue;

	        var MergeResultItemXMLDOMElement = MergeResultItemCollection[Index].m_XMLDOMElement;
	        WinnerConflictsXMLDOMElement.appendChild(MergeResultItemXMLDOMElement);
	        WinnerConflictNodes.push(new FeedSyncItemClass(MergeResultItemXMLDOMElement));
	        }
            
	    return WinnerNode;
        }
            
    function ProcessCollections(i_OuterNodeCollection, i_InnerNodeCollection, io_MergeNodeCollection, i_WinnerNode)
        {
        for (var OuterNodeCollectionIndex = 0; OuterNodeCollectionIndex < i_OuterNodeCollection.length; ++OuterNodeCollectionIndex)
            {
            var OuterNode = i_OuterNodeCollection[OuterNodeCollectionIndex];
            var OuterSyncNode = OuterNode.m_SyncNode;
            var OuterNodeSubsumed = false;
            
            for (var InnerNodeCollectionIndex = 0; InnerNodeCollectionIndex < i_InnerNodeCollection.length; ++InnerNodeCollectionIndex)
                {
                var InnerNode = i_InnerNodeCollection[InnerNodeCollectionIndex];
                if (InnerNode == null)
                    continue;
                                
                var InnerSyncNode = InnerNode.m_SyncNode;
                var OuterHistoryNode = OuterNode.m_SyncNode.m_HistoryNodes[0];        
                
                for (var HistoryIndex = 0; HistoryIndex < InnerSyncNode.m_HistoryNodes.length; ++HistoryIndex)
                    {
                    var InnerHistoryNode = InnerSyncNode.m_HistoryNodes[HistoryIndex];
                    
                    if (!IsNullOrEmpty(OuterHistoryNode.m_By) && (OuterHistoryNode.m_By == InnerHistoryNode.m_By))
                        {
                        OuterNodeSubsumed = (InnerHistoryNode.m_Sequence >= OuterHistoryNode.m_Sequence);
                        break;
                        }
                    else if (IsNullOrEmpty(OuterHistoryNode.m_By) && IsNullOrEmpty(InnerHistoryNode.m_By))
                        {
                        if (!IsNullOrEmpty(InnerHistoryNode.m_When) && !IsNullOrEmpty(OuterHistoryNode.m_When))
                            {
                            OuterNodeSubsumed = (InnerHistoryNode.m_When == OuterHistoryNode.m_When);
                            break;
                            }
                        }
                    }

                if (OuterNodeSubsumed)
                    break;
                }

            if (OuterNodeSubsumed)
                {
                i_OuterNodeCollection[OuterNodeCollectionIndex] = null;
                continue;
                }

            try{
            if (OuterSyncNode.m_ConflictNodes.length > 0)
                OuterSyncNode.m_ConflictsXMLDOMElement.parentNode.removeChild(OuterSyncNode.m_ConflictsXMLDOMElement);
                }
                catch(e)
                {debugger;
                }
                

            io_MergeNodeCollection[io_MergeNodeCollection.length] = OuterNode;

            var NeedToAssignWinner = 
                (i_WinnerNode == null) || 
                (-1 == i_WinnerNode.Compare(OuterNode));
                
            if (NeedToAssignWinner)
                i_WinnerNode = OuterNode;
            }

        return i_WinnerNode;
        }

    function _CompareItem(i_IncomingItem)
        {
	    //  This function compares the two Nodes and returns:
	    //     1 if local item is newer than incoming item
	    //    -1 if incoming item is newer than local item
	    //     0 if items are equal
	    //     null if items are equal but conflict data is different
	    //
        if (i_IncomingItem.m_SyncNode.m_Updates > this.m_SyncNode.m_Updates)
            return -1;

        if (i_IncomingItem.m_SyncNode.m_Updates == this.m_SyncNode.m_Updates)
            {
            var LocalTopMostHistoryNode = this.m_SyncNode.m_HistoryNodes[0];
            var IncomingTopMostHistoryNode = i_IncomingItem.m_SyncNode.m_HistoryNodes[0];
            
            if (IsNullOrEmpty(LocalTopMostHistoryNode.m_When) && !IsNullOrEmpty(IncomingTopMostHistoryNode.m_When))
                return -1;
            
            if (!IsNullOrEmpty(LocalTopMostHistoryNode.m_When) && !IsNullOrEmpty(IncomingTopMostHistoryNode.m_When))
                {
                if (LocalTopMostHistoryNode.m_When < IncomingTopMostHistoryNode.m_When)
                    return -1;
                    
                if (LocalTopMostHistoryNode.m_When > IncomingTopMostHistoryNode.m_When)
                    return 1;
                }
                
            if (LocalTopMostHistoryNode.m_By > IncomingTopMostHistoryNode.m_By)
                return -1;
                
            if (LocalTopMostHistoryNode.m_By < IncomingTopMostHistoryNode.m_By)
                return 1;

            if (this.m_SyncNode.m_ConflictNodes.length == i_IncomingItem.m_SyncNode.m_ConflictNodes.length)
                {
                if (this.m_SyncNode.m_ConflictNodes.length > 0)
                    {
                    for (var Index = 0; Index < this.m_SyncNode.m_ConflictNodes.length; ++Index)
                        {
                        var LocalItemConflict = this.m_SyncNode.m_ConflictNodes[Index];
                        var MatchingConflictItem = false;
                        
                        for (var Index2 = 0; Index2 < i_IncomingItem.m_SyncNode.m_ConflictNodes.length; ++Index2)
                            {
                            var IncomingItemConflict = i_IncomingItem.m_SyncNode.m_ConflictNodes[Index2];
                            if (IncomingItemConflict.Compare(LocalItemConflict) == 0)
                                {
                                MatchingConflictItem = true;
                                break;
                                }
                            }
                            
                        if (!MatchingConflictItem)
                            return null;
                        }
                    }
                    
                return 0;
                }
            else
		{
		return null;
		}
            }
            
        return -1;
	    }

    function _IsSubsumedBy(i_IncomingItem)
        {
        return this.m_SyncNode.IsSubsumedBy(i_IncomingItem.m_SyncNode);
        }
        
	FeedSyncItemClass.prototype.Update = _UpdateItem;
	FeedSyncItemClass.prototype.ResolveConflicts = _ResolveItemConflicts;
	FeedSyncItemClass.prototype.Delete = _DeleteItem;
	FeedSyncItemClass.prototype.Clone = _CloneItem;
	FeedSyncItemClass.prototype.Merge = _MergeItem;
	FeedSyncItemClass.prototype.Compare = _CompareItem;
	FeedSyncItemClass.prototype.IsSubsumedBy = _IsSubsumedBy;
	}

function SyncNodeClass(i_Item)
	{
	this.m_Item = i_Item;

	var XMLDOMElement = this.m_Item.m_XMLDOMElement;
	
    var NamespaceDeclarations = new Array();
    NamespaceDeclarations.push("xmlns:sx='" + g_FeedSyncNamespaceURI + "'");

    this.m_XMLDOMElement = SelectSingleNode
        (
        XMLDOMElement,
        "sx:sync",
        NamespaceDeclarations,
        false
        );

	if (this.m_XMLDOMElement == null)
	    throw "Unable to find 'sx:sync' element!";

	this.m_ID = this.m_XMLDOMElement.getAttribute("id");
	if (this.m_ID == null)
	    throw "Unable to find 'id' attribute for 'sx:sync' element!";

	this.m_Updates = this.m_XMLDOMElement.getAttribute("updates");
	if (this.m_Updates == null)
        throw "Unable to find 'updates' attribute for 'sx:sync' element!";

	this.m_Deleted = GetBooleanAttributeValue
	    (
	    this.m_XMLDOMElement,
	    "deleted"
	    );
        
	this.m_NoConflicts = GetBooleanAttributeValue
	    (
	    this.m_XMLDOMElement,
	    "noconflicts"
	    );

    this.m_ConflictNodes = new Array();

    if (!this.m_NoConflicts)
		{
		this.m_ConflictsXMLDOMElement = SelectSingleNode
		    (
		    this.m_XMLDOMElement,
		    "sx:conflicts",
		    NamespaceDeclarations,
		    false
		    );
    	
		if (this.m_ConflictsXMLDOMElement != null)
		    {
		    var ItemXPathQuery = "*[sx:sync]";

		    var ConflictItemXmlDOMElements = SelectNodes
		        (
		        this.m_ConflictsXMLDOMElement,
		        ItemXPathQuery,
		        NamespaceDeclarations,
		        true
		        );
    		
		    for (var Index = 0; Index < ConflictItemXmlDOMElements.length; ++Index)
			    {
			    var ConflictItemXmlDOMElement = ConflictItemXmlDOMElements[Index];
			    this.m_ConflictNodes[Index] = new FeedSyncItemClass(ConflictItemXmlDOMElement);
			    }
		    }
		}

    this.m_HistoryNodes = new Array();

    var HistoryXMLDOMElements = SelectNodes
        (
        this.m_XMLDOMElement,
        "sx:history",
        NamespaceDeclarations,
        true
        );

	if (HistoryXMLDOMElements.length == 0)
	    throw "Unable to find 'sx:history' elements for 'sx:sync' element!";

    for (var Index = 0; Index < HistoryXMLDOMElements.length; ++Index)
	    {
	    var HistoryXMLDOMElement = HistoryXMLDOMElements[Index];
	    this.m_HistoryNodes[Index] = new HistoryNodeClass
	        (
	        this, 
	        HistoryXMLDOMElement
	        );
	    }
	    
    function _IsSubsumedBy(i_IncomingSyncNode)
        {
        var IsSubsumed = false;
        var LocalTopMostHistory = this.m_HistoryNodes[0];

        for (var Index = 0; Index < i_IncomingSyncNode.m_HistoryNodes.length; ++Index)
            {
            var IncomingHistory = i_IncomingSyncNode.m_HistoryNodes[Index];
            
            if (LocalTopMostHistory.IsSubsumedBy(IncomingHistory))
                {
                IsSubsumed = true;
                break;
                }
                    
            }

        if (!IsSubsumed)
            {
            for (var Index = 0; Index < i_IncomingSyncNode.m_ConflictNodes.length; ++Index)
                {
                var IncomingConflictNode = i_IncomingSyncNode.m_ConflictNodes[Index];
                if (this._IsSubsumedBy(IncomingConflictNode.m_SyncNode))
                    {
                    IsSubsumed = true;
                    break;
                    }
                }
            }

        if (!IsSubsumed)
            return false;

        for (var Index = 0; Index < this.m_ConflictNodes.length; ++Index)
            {
            IsSubsumed = false;
            
            var LocalConflictNode = this.m_ConflictNodes[Index];
            if (LocalConflictNode.m_SyncNode.IsSubsumedBy(i_IncomingSyncNode))
                {
                IsSubsumed = true;
                break;
                }

            for (var Index2 = 0; Index2 < i_IncomingSyncNode.m_ConflictNodes.length; ++Index2)
                {
                var IncomingConflictNode = i_IncomingSyncNode.m_ConflictNodes[Index2];
                if (LocalConflictNode.m_SyncNode.IsSubsumedBy(IncomingConflictNode.m_SyncNode))
                    {
                    IsSubsumed = true;
                    break;
                    }
                }
                                
            if (!IsSubsumed)
                return false;
            }

            return true;
        }

    function _Update(i_By, i_When, i_Sequence, i_ResolveConflicts, i_Deleted)
        {
        if (IsNullOrEmpty(i_By) && IsNullOrEmpty(i_When))
            throw "Must provide By or When value!";
            
        if (IsNullOrEmpty(i_When))
            i_When = ConvertDateToRFC3339DateTime();        	
        
        var Updates = this.m_Updates;
        ++Updates;

        if (i_Sequence == null)
            i_Sequence = Updates;

        var NamespaceDeclarations = new Array();
        NamespaceDeclarations[0] = "xmlns:sx='" + g_FeedSyncNamespaceURI + "'";
            
        if (!IsNullOrEmpty(i_By))
            {
            var NewHistoryNodes = new Array();
            
            for (var Index = 0; Index < this.m_HistoryNodes.length; ++Index)
                {
                var History = this.m_HistoryNodes[Index];

                if ((History.m_By == i_By) && (History.m_Sequence >= i_Sequence))
                    i_Sequence = History.m_Sequence + 1;
                
                //  Sparse purge, leaving room at [0] index for new history node
                if (History.m_By != i_By)
                    NewHistoryNodes[NewHistoryNodes.length + 1] = History;
                else
                    History.m_XMLDOMElement.parentNode.removeChild(History.m_XMLDOMElement);
                }
            
            this.m_HistoryNodes = NewHistoryNodes;
            }

        var HistoryXMLDOMElement = CreateXMLDOMElement
            (
            this.m_XMLDOMElement.ownerDocument, 
            "sx:history",
            g_FeedSyncNamespaceURI
            );
            
        HistoryXMLDOMElement.setAttribute("sequence", i_Sequence);
        HistoryXMLDOMElement.setAttribute("when", i_When);
        HistoryXMLDOMElement.setAttribute("by", i_By);

        this.m_XMLDOMElement.insertBefore
            (
            HistoryXMLDOMElement, 
            this.m_XMLDOMElement.childNodes[0]
            );

        var HistoryNode = new HistoryNodeClass(this, HistoryXMLDOMElement);
        this.m_HistoryNodes[0] = HistoryNode;
        
        this.m_XMLDOMElement.setAttribute("updates", Updates);
        this.m_Updates = Updates;

        var NoConflicts = GetBooleanAttributeValue(this.m_XMLDOMElement, "noconflicts");
        if (i_Deleted != null)
            {
            this.m_XMLDOMElement.setAttribute("deleted", i_Deleted ? "true" : "false");
            this.m_Deleted = i_Deleted;
            }
        
        var NeedToResolveConflicts = 
            !NoConflicts && 
            i_ResolveConflicts && 
            (this.m_ConflictNodes.length > 0);
            
        if (NeedToResolveConflicts)
            {
            for (var ConflictItemIndex = 0; ConflictItemIndex < this.m_ConflictNodes.length; ++ConflictItemIndex)
                {
                var ConflictFeedSyncItem = this.m_ConflictNodes[ConflictItemIndex];
                
                for (var ConflictHistoryIndex = 0; ConflictHistoryIndex < ConflictFeedSyncItem.m_SyncNode.m_HistoryNodes.length; ++ConflictHistoryIndex)
                    {
                    var ConflictHistoryNode = ConflictFeedSyncItem.m_SyncNode.m_HistoryNodes[ConflictHistoryIndex];

                    if (((i_By != null) && (i_By == ConflictHistoryNode.m_By)) && (ConflictHistoryNode.m_Sequence >= i_Sequence))
                        {
                        i_Sequence = ConflictHistoryNode.m_Sequence + 1;
                        HistoryXMLDOMElement.setAttribute("sequence", i_Sequence);
                        }

                    var ConflictHistoryRepresented = false;
                    
                    for (var HistoryIndex = 0; HistoryIndex < this.m_HistoryNodes.length; ++HistoryIndex)
                        {
                        var HistoryNode = this.m_HistoryNodes[HistoryIndex];

                        if ((HistoryNode.m_By != null) && (HistoryNode.m_By == ConflictHistoryNode.m_By))
                            {
                            if (HistoryNode.m_Sequence >= ConflictHistoryNode.m_Sequence)
                                ConflictHistoryRepresented = true;

                            break;
                            }
                        else if ((HistoryNode.m_By == null) && (ConflictHistoryNode.m_By == null))
                            {
                            if ((HistoryNode.m_When != null) && (ConflictHistoryNode.m_When != null))
                                {
                                if (HistoryNode.m_When == ConflictHistoryNode.m_When)
                                    {
                                    ConflictHistoryRepresented = true;
                                    break;
                                    }
                                }
                            }
                        }
                        
                    if (ConflictHistoryRepresented)
                        continue;
                    
                    var ClonedConflictHistoryXMLDOMElement = ConflictHistoryNode.m_XMLDOMElement.cloneNode(true);

                    this.m_XMLDOMElement.insertBefore
                        (
                        ClonedConflictHistoryXMLDOMElement, 
                        HistoryXMLDOMElement.nextSibling
                        );
                    }
                }
                       
            var NamespaceDeclarations = new Array();
            NamespaceDeclarations.push("xmlns:sx='" + g_FeedSyncNamespaceURI + "'");

            this.m_ConflictsXMLDOMElement.parentNode.removeChild(this.m_ConflictsXMLDOMElement);
            this.m_ConflictsXMLDOMElement = null;
            this.m_ConflictNodes.length = 0;
            }
        }
        
    SyncNodeClass.prototype.IsSubsumedBy = _IsSubsumedBy;        
    SyncNodeClass.prototype.Update = _Update;
    }

function HistoryNodeClass(i_SyncNode, i_HistoryXMLDOMElement)
	{
	this.m_SyncNode = i_SyncNode;
	this.m_XMLDOMElement = i_HistoryXMLDOMElement;
	
	if (this.m_XMLDOMElement == null)
        throw "Unable to find 'sx:history' element for 'sx:sync'!";

	this.m_Sequence = this.m_XMLDOMElement.getAttribute("sequence");
	this.m_When = this.m_XMLDOMElement.getAttribute("when");
	this.m_By = this.m_XMLDOMElement.getAttribute("by");
	
	if ((this.m_When == null) && (this.m_By == null))
        throw "Unable to find 'when' or 'by' attribute in 'sx:history' element for 'sx:sync'!";
        
    function _IsSubsumedBy(i_IncomingHistoryNode)
        {
        var Subsumed = false;

        if (!IsNullOrEmpty(this.m_By))
            {
            Subsumed =
                (this.m_By == i_IncomingHistoryNode.m_By) &&
                (i_IncomingHistoryNode.m_Sequence >= this.m_Sequence);
            }
        else
            {
            Subsumed =
                (this.m_When == i_IncomingHistoryNode.m_When) &&
                (this.m_Sequence == i_IncomingHistoryNode.m_Sequence);
            }

        return Subsumed;
        }
        
        HistoryNodeClass.prototype.IsSubsumedBy = _IsSubsumedBy;                
	}

